package main

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"strings"
)

type Attr struct {
	Name      string
	Value     string
	File      string
	IsMap     bool
	FieldType string
}

const pack = "com.lonntec.sue.bill.setting.xml.bean"

//const pack="com.example.li.myapplication.xml"
const javaDir = `E:\project\l2\lonntec-sue\Android-Source\app\src\main\java\com\lonntec\sue\bill\setting\xml\bean`

//const javaDir=`E:\tmp\accessbilityserviceDemon\app\src\main\java\com\example\li\myapplication\xml`
const annotation = "com.lonntec.sue.xmlSerial.MaperAnnotation"

//const annotation="com.example.li.myapplication.xmlSerial.MaperAnnotation"
func (attr *Attr) is(attrStr string) bool {
	return attr.Name == attrStr
}
func (set *Node) AddChild(childname, rname string) {
	for _, v := range set.Childs {
		if v.name == childname {
			return
		}
	}
	set.Childs = append(set.Childs, &Child{name: childname, rname: rname})
}

type Node struct {
	Attrs    []*Attr
	Childs   []*Child
	Names    []string
	RName    string
	IsStruct bool
}

func (node *Node) addName(name string) {
	for _, v := range node.Names {
		if strings.ToLower(v) == strings.ToLower(name) {
			return
		}
	}
	node.Names = append(node.Names, name)
}
func (node *Node) getNamesStr() string {
	_bs := &bytes.Buffer{}
	for _, v := range node.Names {
		_bs.WriteString("\"" + v + "\",")
	}
	_bs.Truncate(len([]byte(",")))
	return string(_bs.Bytes())
}

type Child struct {
	name  string
	rname string
}

func (set *Node) AddAttr(name, val, file string, langs langs) {
	for _, v := range set.Attrs {
		if v.is(name) {
			return
		}
	}
	if langs.contain(name) {
		set.AddAttr("language", "zhcn", "", langs)
		return
	}
	set.Attrs = append(set.Attrs, &Attr{Name: name, Value: val, File: file, IsMap: name == "language"})
}

//收集xml的属性
func main() {
	_dir := `E:\project\l2\lonntec-sue\Android-Source\app\src\main\assets\Compact_[采购入库-采购订单].xml` //`C:\Users\li\Desktop\xml`
	//_dir := `C:\Users\li\Desktop\xml`
	//_dir := `C:\Users\li\Desktop\xml`
	nodes := make(map[string]*Node)
	filepath.Walk(_dir, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			fiel, err := os.Open(path)
			if err != nil {
				fmt.Printf("file:%s,error:%s\n", path, err.Error())
			} else {
				fmt.Printf("file:%s,ok\n", path)
			}
			lang := xmllanguage(fiel)
			_, err = fiel.Seek(0, os.SEEK_SET)
			if err != nil {
				fmt.Printf("file:%s,error:%s\n", path, err.Error())
			} else {
				fmt.Printf("file:%s,ok\n", path)
			}
			xmldo(fiel, nodes, path, langs(lang))
		}
		return nil
	})
	//附加上附加属性
	for _, node := range nodes {
		set := getNodeSetByRname(node.RName)
		if set != nil {
			node.Attrs = append(node.Attrs, set.Attrs...)
		}
	}
	_bytes, err := json.Marshal(&struct {
		Root map[string]*Node
	}{Root: nodes})
	if err != nil {
		fmt.Printf("marshal error:%s\n", err.Error())
	}
	fmt.Printf("%s", string(_bytes))
	//pack:="com.example.li.myapplication.xml"
	//pack:="com.lonntec.sue.bill.setting.xml"
	class := make([]*JavaClass, 0, len(nodes))
	prefx := "Xml"
	for _, v := range nodes {
		_class := &JavaClass{OriName: v.Names, Pack: pack, Name: toUpperFirst(transJavaName(v.RName)), Prefx: prefx}
		_class.AddAttrFields(v.Attrs)
		_class.AddAttrField(&Attr{FieldType: "Integer", Name: "elementIndex"})
		_class.AddAttrField(&Attr{FieldType: "Integer", Name: "elementParentIndex"})
		_class.AddChildNodeFields(v.Childs, nodes)
		class = append(class, _class)
		fmt.Printf("%s\n", _class.toJavaFile())
	}
	//javaDir:=`E:\tmp\accessbilityserviceDemon\app\src\main\java\com\example\li\myapplication\xml`
	//javaDir:=`E:\project\l2\lonntec-sue\Android-Source\app\src\main\java\com\lonntec\sue\bill\setting\xml`

	//处理language

	os.RemoveAll(javaDir)
	os.MkdirAll(javaDir, 0666)
	for _, v := range class {
		f, err := os.OpenFile(path.Join(javaDir, fmt.Sprintf("%s.java", v.getName())), os.O_TRUNC|os.O_CREATE|os.O_RDWR, 0666)
		if err != nil {
			fmt.Printf("java:%s error:%s\n", v.Name, err.Error())
		}
		defer f.Close()
		f.WriteString(v.toJavaFile())
		f.Sync()
		fmt.Printf("java:%s ok\n", v.getName())
	}
	fmt.Printf("end................\n")
}

func genJava(nodename, pack string, nodes map[string]*Node, ) {

}

type JavaField struct {
	IsAttr  bool
	OriName string
	Name    string //小写开头
	Type    string
	Prefx   string
}

func (JavaField *JavaField) getType() string {
	return JavaField.Type
}

func (JavaField *JavaField) toJavaFieldString() string {
	if JavaField.IsAttr {
		return fmt.Sprintf("\t@MaperAnnotation(alias = \"%s\",isAttr = %s)\n\tprivate %s %s;\n", JavaField.OriName, "true", JavaField.getType(), JavaField.Name)
	} else {
		return fmt.Sprintf("\t@MaperAnnotation(alias = \"%s\",isAttr = %s)\n\tprivate %s %s;\n", JavaField.OriName, "false", JavaField.getType(), JavaField.Name)
	}
}
func (JavaField *JavaField) toJavaGetter() string {
	return fmt.Sprintf("\tpublic %s get%s(){\n\treturn %s;\n}\n", JavaField.getType(), toUpperFirst(JavaField.Name), JavaField.Name)
}
func (JavaField *JavaField) toJavaSetter() string {
	return fmt.Sprintf("\tpublic void set%s(%s %s){\n\tthis.%s=%s;\n}\n", toUpperFirst(JavaField.Name), JavaField.getType(), JavaField.Name, JavaField.Name, JavaField.Name)
}
func toUpperFirst(str string) string {
	if str == "" {
		return str
	}
	_bytes := []byte(str)
	_byte := _bytes[0]
	if _byte >= 'a' && _byte <= 'z' {
		_bytes[0] = _byte - 'a' + 'A'
	}
	return string(_bytes)
}
func toLowerFirst(str string) string {
	if str == "" {
		return str
	}
	_bytes := []byte(str)
	_byte := _bytes[0]
	if _byte > 'A' && _byte < 'Z' {
		_bytes[0] = _byte + 'a' - 'A'
	}
	return string(_bytes)
}

type JavaClass struct {
	Pack    string
	Name    string
	OriName []string
	Fields  []*JavaField
	Imports []string
	Prefx   string
}

func transJavaName(oriName string) string {
	_buf := &bytes.Buffer{}
	for i := 0; i < len(oriName); i++ {
		if oriName[i] == '-' {
			i++
			if i == len(oriName)-1 {
				break
			}
			if oriName[i] > 'a' && oriName[i] < 'z' {
				_buf.WriteByte(oriName[i] - 'a' + 'A')
			} else {
				_buf.WriteByte(oriName[i])
			}
		} else {
			if i == 0 {
				_buf.WriteString(strings.ToLower(string(oriName[i])))
			} else {
				_buf.WriteString(string(oriName[i]))
			}
		}
	}
	return string(_buf.Bytes())
}
func (JavaClass *JavaClass) AddField(field *JavaField) {
	for _, v := range JavaClass.Fields {
		if strings.ToLower(v.Name) == strings.ToLower(field.Name) {
			return
		}
	}
	field.Prefx = JavaClass.Prefx
	JavaClass.Fields = append(JavaClass.Fields, field)
}
func (JavaClass *JavaClass) AddAttrField(attr *Attr) {
	field := &JavaField{OriName: attr.Name, IsAttr: true}
	if attr.IsMap {
		field.Type = "Map<String,String>"
	} else if attr.FieldType != "" {
		field.Type = attr.FieldType
	} else if lo := strings.ToLower(attr.Value); lo == "true" || lo == "false" {
		field.Type = "Boolean"
	} else if _, err := strconv.ParseInt(attr.Value, 10, 32); err == nil {
		field.Type = "Integer"
	} else if _, err := strconv.ParseFloat(attr.Value, 32); err == nil {
		field.Type = "Float"
	} else {
		field.Type = "String"
	}
	field.Name = transJavaName(attr.Name)
	JavaClass.AddField(field)
}
func (JavaClass *JavaClass) AddAttrFields(attr []*Attr) {
	for _, v := range attr {
		JavaClass.AddAttrField(v)
	}
}
func (JavaClass *JavaClass) AddChildNodeFields(name []*Child, nodes map[string]*Node) {
	for _, v := range name {
		JavaClass.AddChildNodeField(v, nodes)
	}
}
func (JavaClass *JavaClass) AddChildNodeField(child *Child, nodes map[string]*Node) {
	fmt.Printf("-------%+v\n", child)
	field := &JavaField{OriName: child.name}
	if node, ok := nodes[strings.ToLower(child.rname)]; ok {
		if node.IsStruct {
			field.Type = fmt.Sprintf("%s", "Xml"+toUpperFirst(child.rname))
			field.Name = transJavaName(child.name)
		} else {
			field.Type = fmt.Sprintf("List<%s>", "Xml"+toUpperFirst(child.rname))
			field.Name = transJavaName(child.name) + "s"

		}
	} else {
		field.Type = fmt.Sprintf("List<%s>", toUpperFirst(child.rname))
		field.Name = transJavaName(child.name) + "s"
	}
	JavaClass.AddField(field)
	fmt.Printf("-------%+v\n", field)
}
func (JavaClass *JavaClass) addImports(str string) {
	for _, v := range JavaClass.Imports {
		if v == str {
			return
		}
	}
	JavaClass.Imports = append(JavaClass.Imports, str)
}
func (JavaClass *JavaClass) cleanImports() {
	JavaClass.Imports = JavaClass.Imports[0:0]
	JavaClass.addImports(annotation)
	JavaClass.addImports("java.io.Serializable")
	JavaClass.addImports("com.lonntec.sue.bill.components.xml.IElementIndex")
	//JavaClass.addImports("com.lonntec.sue.bill.components.xml.IElementIndex")
	//JavaClass.addImports("com.example.li.myapplication.xmlSerial.MaperAnnotation")
}
func (JavaClass *JavaClass) getName() string {
	return fmt.Sprintf("%s%s", JavaClass.Prefx, JavaClass.Name)
}
func (JavaClass *JavaClass) toJavaFile() string {
	_buf := bytes.Buffer{}
	//添加import
	JavaClass.cleanImports()
	for _, v := range JavaClass.Fields {
		switch {
		case strings.Index(v.Type, "List") == 0:
			JavaClass.addImports("java.util.List")
			break
		case strings.Index(v.Type, "Map") == 0:
			JavaClass.addImports("java.util.Map")
			break
		case v.Type == "XmlHAlign":
			JavaClass.addImports("com.lonntec.sue.bill.setting.xml.XmlHAlign")
			break
		case v.Type == "XmlOrientation":
			JavaClass.addImports("com.lonntec.sue.bill.setting.xml.XmlOrientation")
			break
		case v.Type == "XmlUIMode":
			JavaClass.addImports("com.lonntec.sue.bill.setting.xml.XmlUIMode")
			break
		case v.Type == "XmlVAlign":
			JavaClass.addImports("com.lonntec.sue.bill.setting.xml.XmlVAlign")
			break
		case v.Type == "Rect":
			JavaClass.addImports("android.graphics.Rect")
			break
		case v.Type == "Color":
			JavaClass.addImports("android.graphics.Color")
			break
		case v.Type == "XmlFieldType":
			JavaClass.addImports("com.lonntec.sue.bill.setting.xml.XmlFieldType")
			break
		case v.Type == "XmlDataColumnSet":
			JavaClass.addImports("com.lonntec.sue.bill.setting.xml.bean.XmlDataColumnSet")
			break

		default:
			break
		}
	}
	_buf.WriteString(fmt.Sprintf("package %s;", JavaClass.Pack))
	_buf.WriteString("\n")
	for _, v := range JavaClass.Imports {
		_buf.WriteString(fmt.Sprintf("import %s;\n", v))
	}
	_buf.WriteString("\n")
	_buf.WriteString(fmt.Sprintf("public class %s implements Serializable,IElementIndex {", JavaClass.getName()))
	_buf.WriteString("\n")
	for _, v := range JavaClass.Fields {
		_buf.WriteString(v.toJavaFieldString())
	}
	_buf.WriteString("\n")
	for _, v := range JavaClass.Fields {
		_buf.WriteString(v.toJavaGetter())
		_buf.WriteString(v.toJavaSetter())
	}
	_buf.WriteString("}")
	return string(_buf.Bytes())
}

type NodeStatck []*Node

func (NodeStatck *NodeStatck) push(node *Node) {
	*NodeStatck = append(*NodeStatck, node)
}
func (NodeStatck *NodeStatck) pop() *Node {
	if len(*NodeStatck) == 0 {
		return nil
	}
	node := (*NodeStatck)[len(*NodeStatck)-1]
	*NodeStatck = (*NodeStatck)[:len(*NodeStatck)-1]
	return node
}
func (NodeStatck *NodeStatck) current() *Node {
	if len(*NodeStatck) == 0 {
		return nil
	}
	return (*NodeStatck)[len(*NodeStatck)-1]
}

type NodeSet struct {
	ParentNodeName string
	ToNodeName     string
	NodeNames      []string
	Attrs          []*Attr
	StructNodes    []string
	ListNodes      []string
	aliasToTypeIfParentIsSameStruct bool
}

func getNodeSetByRname(rname string) *NodeSet {
	for _, set := range nodeSets {
		if strings.ToLower(set.ToNodeName) == strings.ToLower(rname) {
			return set
		}
	}
	return nil
}
func getNodeSet(nodeName string, statck NodeStatck) *NodeSet {
	for _, node := range statck {
		for _, set := range nodeSets {
			for _, name := range node.Names {
				if strings.ToLower(name) == strings.ToLower(set.ParentNodeName) {
					for _, name := range set.NodeNames {
						if nodeName == strings.ToLower(name) {
							fmt.Println("-------------------------------------------", nodeName, name)
							return set
						}
					}
				}
			}

		}
	}
	return nil
}

var nodeSets []*NodeSet

func init() {
	nodeSets = []*NodeSet{
		{ParentNodeName:     "terminalfunctional",
			NodeNames:   []string{"language", "dataSource", "uiLayout"},
			ToNodeName:  "",
			StructNodes: []string{"language", "dataSource", "uiLayout"},
		},
		{ParentNodeName:     "dataSource",
			NodeNames:   []string{"scanDetail", "scanHead", "sourceDetail", "sourceHead"},
			ToNodeName:  "dataSet",
			StructNodes: []string{"scanDetail", "scanHead", "sourceDetail", "sourceHead"},
		},
		{
			ParentNodeName: "dataSource",
			NodeNames:      []string{"column"}, ToNodeName: "dataColumnSet",
		},
		{
			ParentNodeName: "uiLayout",
			NodeNames: []string{"sourceHeadListView", "scanDetailEditors", "scanHeadEditors",
					    "scanDetailListView", "scanHeadListView", "sourceDetailListView",
					    "lineLayout", "column", "editor", "item", "lookup",
			},
			aliasToTypeIfParentIsSameStruct:  true,
			ToNodeName: "UiElement",
			Attrs: []*Attr{
				{Name: "uiMode", FieldType: "XmlUIMode"},
				{Name: "orientation", FieldType: "XmlOrientation"},
				{Name: "padding", FieldType: "Rect"},
				{Name: "margin", FieldType: "Rect"},
				{Name: "gravity", FieldType: "Float"},
				{Name: "textColor", FieldType: "Color"},
				{Name: "editColor", FieldType: "Color"},
				{Name: "labelColor", FieldType: "Color"},
				{Name: "textSize", FieldType: "Float"},
				{Name: "width", FieldType: "Float"},
				{Name: "height", FieldType: "Float"},
				{Name: "weight", FieldType: "Float"},
				{Name: "align", FieldType: "XmlHAlign"},
				{Name: "selfAlign", FieldType: "XmlHAlign"},
				{Name: "verticalAlign", FieldType: "XmlVAlign"},
				{Name: "selfVerticalAlign", FieldType: "XmlVAlign"},
				{Name: "labelWidth", FieldType: "Float"},
				{Name: "background", FieldType: "String"},
				{Name: "columnWidths", FieldType: "String"},
				{Name: "col", FieldType: "Integer"},
				{Name: "row", FieldType: "Integer"},
				{Name: "colSpan", FieldType: "Integer"},
				{Name: "rowSpan", FieldType: "Integer"},
				{Name: "childs", FieldType: "List<XmlUiElement>"},
			},
		},
		/*{
			ParentNodeName: "uiLayout",
			NodeNames:      []string{"column", "editor", "item", "lookup"},
			ToNodeName:     "UiColumnSet",
			Attrs: []*Attr{
				{Name: "uiMode", FieldType: "XmlUIMode"},
				{Name: "col", FieldType: "Integer"},
				{Name: "row", FieldType: "Integer"},
				{Name: "colSpan", FieldType: "Integer"},
				{Name: "rowSpan", FieldType: "Integer"},
				{Name: "orientation", FieldType: "XmlOrientation"},
				{Name: "padding", FieldType: "Rect"},
				{Name: "margin", FieldType: "Rect"},
				{Name: "gravity", FieldType: "Float"},
				{Name: "textColor", FieldType: "Color"},
				{Name: "editColor", FieldType: "Color"},
				{Name: "labelColor", FieldType: "Color"},
				{Name: "textSize", FieldType: "Float"},
				{Name: "width", FieldType: "Float"},
				{Name: "height", FieldType: "Float"},
				{Name: "weight", FieldType: "Float"},
				{Name: "align", FieldType: "XmlHAlign"},
				{Name: "selfAlign", FieldType: "XmlHAlign"},
				{Name: "verticalAlign", FieldType: "XmlVAlign"},
				{Name: "selfVerticalAlign", FieldType: "XmlVAlign"},
				{Name: "labelWidth", FieldType: "Float"},
				{Name: "background", FieldType: "String"},
				{Name: "fieldType", FieldType: "XmlFieldType"},
				{Name: "dataColumnSet", FieldType: "XmlDataColumnSet"},
				{Name: "elementIndexs", FieldType: "List<IElementIndex>"},
				{Name: "lineLayouts", FieldType: "List<XmlUiContainer>"},
			},
		}*/
	}
}

//对一份xml的解析
//收集相关的节点和属性，生成java类
func xmldo(reader io.Reader, nodes map[string]*Node, fiel string, langs langs) {
	_decoder := xml.NewDecoder(reader)
	nodeStatck := make(NodeStatck, 0, 50)
	var rname string
	//映射设置
	for t, err := _decoder.Token(); err == nil; t, err = _decoder.Token() {
		switch token := t.(type) {
		case xml.StartElement:
			parentNode := nodeStatck.current()
			name := strings.Trim(token.Name.Local, " ")
			set := getNodeSet(strings.ToLower(name), nodeStatck)
			isStruct := false
			if set != nil {
				if set.ToNodeName != "" {
					rname = set.ToNodeName
				}
				for _, v := range set.StructNodes {
					if strings.ToLower(v) == strings.ToLower(name) {
						isStruct = true
					}
				}
			}
			if set == nil || set.ToNodeName == "" {
				rname = name
			}
			lrname := strings.ToLower(rname)
			elem, ok := nodes[lrname]
			if !ok {
				elem = &Node{Attrs: make([]*Attr, 0), Childs: make([]*Child, 0), Names: []string{name}, RName: rname, IsStruct: isStruct}
				nodes[lrname] = elem
			}
			for _, attr := range token.Attr {
				elem.AddAttr(attr.Name.Local, attr.Value, fiel, langs)
			}
			if parentNode != nil {
				parentNode.AddChild(name, rname)
			}
			nodeStatck.push(elem)
			break
		case xml.EndElement:
			nodeStatck.pop()
			break
		case xml.CharData:
		default:

		}
	}
}

type langs []string

func (langs *langs) contain(str string) bool {
	for _, v := range *langs {
		if strings.ToLower(str) == v {
			return true
		}
	}
	return false
}
func xmllanguage(reader io.Reader) []string {
	_decoder := xml.NewDecoder(reader)
	langs := make([]string, 0, 10)
	islangnode := false
	for t, err := _decoder.Token(); err == nil; t, err = _decoder.Token() {
		switch token := t.(type) {
		case xml.StartElement:
			if strings.ToLower(token.Name.Local) == "language" {
				islangnode = true
			}
			if islangnode && strings.ToLower(token.Name.Local) == "item" {
				for _, v := range token.Attr {
					if strings.ToLower(v.Name.Local) == "name" {
						langs = append(langs, strings.ToLower(v.Value))
					}
				}
			}
			break
		case xml.EndElement:
			if strings.ToLower(token.Name.Local) == "language" {
				islangnode = false
				return langs
			}

			break
		case xml.CharData:
		default:

		}
	}
	return langs
}
